// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#pragma once

#include <functional>
#include <string>
#include <utility>
#include <vector>

#include "absl/hash/hash.h"
#include "coin2/base/log.h"
#include "coin2/base/string_util.h"
#include "coin2/exchange/base/market/enums.h"

namespace coin2::exchange::base::market {

// (market, exchange, api) tuple
// a snake-representation exists for serializations whose '.' has a special meaning
struct MarketExchangeApi {
  MarketType market;
  ExchangeType exchange;
  std::string api;  // not empty

  // 'Spot.Okex.v3'
  static MarketExchangeApi FromString(const std::string_view& s);
  static std::optional<MarketExchangeApi> FromStringNoThrow(const std::string_view& s);

  // 'spot_huobi_v1'
  // 'spot_okex_v3'
  // 'okex', 'okex_v3' are not allowed
  static MarketExchangeApi FromSnakeString(const std::string_view& s);
  static std::optional<MarketExchangeApi> FromSnakeStringNoThrow(const std::string_view& s);

  std::string String() const;
  std::string SnakeString() const;

  template <typename H>
  friend H AbslHashValue(H h, const MarketExchangeApi& mea) {
    return H::combine(std::move(h), mea.market, mea.exchange, mea.api);
  }
};

inline bool operator<(const MarketExchangeApi& lhs, const MarketExchangeApi& rhs) {
  return std::tie(lhs.market, lhs.exchange, lhs.api) < std::tie(rhs.market, rhs.exchange, rhs.api);
}

inline bool operator==(const MarketExchangeApi& lhs, const MarketExchangeApi& rhs) {
  return std::tie(lhs.market, lhs.exchange, lhs.api) == std::tie(rhs.market, rhs.exchange, rhs.api);
}

struct MarketExchangeApiRecipe {
  MarketExchangeApi mea;
  std::string recipe;
  std::string String() const {
    // e.g. Spot.Huobi.v1
    return mea.String() + "." + recipe;
  }

  static MarketExchangeApiRecipe FromString(const std::string_view& s) {
    const char* delims = ".";
    std::vector<std::string> vec;
    vec = Split(s, delims);
    CHECK_EQ_THROW(vec.size(), 4);
    CHECK_GT_THROW(vec[2].size(), 0);
    CHECK_GT_THROW(vec[3].size(), 0);

    return MarketExchangeApiRecipe{
        MarketExchangeApi{
            StringToMarketType(vec[0]).value(),  // throw on nullopt
            StringToExchangeType(vec[1]).value(),
            vec[2]},
        vec[3]};
  }

  template <typename H>
  friend H AbslHashValue(H h, const MarketExchangeApiRecipe& self) {
    return H::combine(std::move(h), self.mea, self.recipe);
  }
};

inline bool operator==(const MarketExchangeApiRecipe& lhs, const MarketExchangeApiRecipe& rhs) {
  return lhs.mea == rhs.mea && lhs.recipe == rhs.recipe;
}

inline MarketExchangeApi operator "" _mea(const char* mea_str, size_t size) {
  return MarketExchangeApi::FromString(std::string(mea_str, size));
}

}  // namespace coin2::exchange::base::market

using coin2::exchange::base::market::MarketExchangeApi;
using coin2::exchange::base::market::MarketExchangeApiRecipe;

// Hsah for MarketExchangeApi
// so we can use unordered_map<MEA, _>.
namespace std {
template <>
struct hash<MarketExchangeApi> {
  std::size_t operator()(const MarketExchangeApi& mea) const {
    return absl::Hash<MarketExchangeApi>{}(mea);
  }
};

template <>
struct hash<MarketExchangeApiRecipe> {
  std::size_t operator()(const MarketExchangeApiRecipe& mear) const {
    return absl::Hash<MarketExchangeApiRecipe>{}(mear);
  }
};

}  // namespace std
